<script lang="ts" setup>
import { computed, defineEmits, ref, watch } from "vue"
import NP from "number-precision"
const emit = defineEmits(["movestart", "moving", "moveend", "update:modelValue", "change"])
const props = defineProps({
  modelValue: {
    type: Number,
    default: 1,
  },
  min: {
    type: Number,
    default: 1,
  },
  max: {
    type: Number,
    default: 100,
  },
  step: {
    type: Number,
    default: 1,
  },
})

const isDragging = ref(false)
const trackRef = ref<HTMLElement>()
const trackRect = ref<DOMRect>()

const endValue = ref(props.modelValue)

const computedValue = computed<number>(() => {
  const value = isDragging.value ? endValue.value : props.modelValue

  return NP.round(value, 3)
})

watch(
  () => props.modelValue,
  (newValue) => {
    if (isDragging.value) return
    endValue.value = newValue
  }
)

const getOffsetPercent = (value: number, [min, max]: [number, number]): string => {
  const percent = Math.max((value - min) / (max - min), 0)
  return `${NP.round(percent * 100, 2)}%`
}

const getBtnStyle = () => {
  return {
    left: getOffsetPercent(computedValue.value, [props.min, props.max]),
  }
}
const getBarStyle = () => {
  return {
    left: getOffsetPercent(0, [props.min, props.max]),
    right: getOffsetPercent(props.max + props.min - computedValue.value, [props.min, props.max]),
  }
}

const onMousedownHandle = (e: MouseEvent) => {
  console.log(e)
  e.preventDefault()

  isDragging.value = true
  window.addEventListener("mousemove", onMousemoveHandle)
  window.addEventListener("mouseup", onMouseupHandle)
  window.addEventListener("contextmenu", onMouseupHandle)

  emit("movestart")

  if (trackRef.value) {
    trackRect.value = trackRef.value.getBoundingClientRect()
  }
}

const onMousemoveHandle = (e: MouseEvent) => {
  emit("moving", e.clientX, e.clientY)
  endValue.value = getValueByCoords(e.clientX - 4)

  emit("update:modelValue", endValue.value)
}
const onMouseupHandle = () => {
  isDragging.value = false
  window.removeEventListener("mousemove", onMousemoveHandle)
  window.removeEventListener("mouseup", onMouseupHandle)
  emit("moveend")
  emit("change", endValue.value)
}

// 通过坐标获取value值
const getValueByCoords = (x: number): number => {
  if (!trackRect.value) {
    return 0
  }
  const { left, width } = trackRect.value

  const trackLength = width

  const stepLength = (trackLength * props.step) / (props.max - props.min)
  let diff = x - left
  if (diff < 0) diff = 0
  if (diff > trackLength) diff = trackLength

  // 根据diff计算步数
  const steps = Math.round(diff / stepLength)

  return NP.plus(props.min, NP.times(steps, props.step))
}

const onTrackClickHandle = (e: MouseEvent) => {
  const { clientX } = e
  if (trackRef.value) {
    trackRect.value = trackRef.value.getBoundingClientRect()
  }

  endValue.value = getValueByCoords(clientX)
  emit("update:modelValue", endValue.value)
  emit("change", endValue.value)
}
</script>

<template>
  <div class="slider">
    <div ref="trackRef" class="slider-track" @click="onTrackClickHandle">
      <div class="slider-bar" :style="getBarStyle()"></div>
    </div>
    <div class="slider-btn" :style="getBtnStyle()" @click.stop @mousedown="onMousedownHandle"></div>
  </div>
</template>
<style scoped>
.slider {
  --slider-bar-height: 14px;
  --slider-btn-height: 10px;
  --slider-bg: #f2f4f7;
  --slider-bar-color: #98a2b3;
  --slider-dot-color: var(--slider-bar-color);
  width: 100%;
  position: relative;
}
.slider-track {
  position: relative;
  height: var(--slider-bar-height);
  background-color: var(--slider-bg);
  border-radius: var(--slider-bar-height);
  overflow: hidden;
}

.slider-bar {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: var(--slider-bar-height);
  background-color: var(--slider-bar-color);
}

.slider-btn {
  position: absolute;
  top: 0;
  width: var(--slider-bar-height);
  height: var(--slider-bar-height);
  cursor: pointer;
  /* transform: translateX(-50%); */
}
.slider-track:hover .slider-btn::after {
  /* transform: translate(-50%, -50%) scale(1.2); */
}
.slider-btn::after {
  content: "";
  position: absolute;
  /* left: 50%; */
  top: calc(var(--slider-btn-height) * -1);
  /* top: -calc(var(--slider-btn-height)); */
  display: inline-block;
  box-sizing: border-box;

  width: var(--slider-btn-height);
  height: var(--slider-btn-height);
  /* background: #7540ee; */
  background: var(--el-color-primary-light-2);
  border-radius: 50%;
  /* border: 2px solid var(--slider-dot-color); */
  /* transform: translate(-50%, -50%); */
  transform: translateX(-50%);
  transition: all 0.3s cubic-bezier(0.3, 1.3, 0.3, 1);
}
.slider-btn::before {
  content: "";
  position: absolute;
  display: inline-block;
  transform: translateX(-50%);
  z-index: 9;
  height: 94px;
  width: 2px;
  background: var(--el-color-primary-light-2);
}
</style>
